Lambda 関数を利用した EC2 Auto Scaling グループ所属インスタンスの AZ 別自動連番付けをやってみた

Lambda 関数を利用した EC2 Auto Scaling グループ所属インスタンスの AZ 別自動連番付けをやってみた

Clock Icon2025.01.20

はじめに

テクニカルサポートの 片方 です。
Lambda 関数を利用した、EC2 Auto Scaling インスタンスの AZ 別自動連番をやってみました。
今回は、2 パターン作成してます。
例えば 2AZ 環境の場合は以下のように割り当てられます。

  • 起動時間による番号の決定
    古い起動時間の EC2 インスタンスから若い番号を割り当て

  • AZ ごとの番号割り当て
    ap-northeast-1a → 奇数番号(01, 03, 05...)
    ap-northeast-1c → 偶数番号(02, 04, 06...)

005

3AZ の場合は以下のように割り当てられます。

  • 起動時間による番号の決定
    古い起動時間の EC2 インスタンスから若い番号を割り当て

  • AZ ごとの番号割り当て
    ap-northeast-1a → 01-1a, 02-1a, 03-1a
    ap-northeast-1c → 01-1c, 02-1c, 03-1c
    ap-northeast-1d → 01-1d, 02-1c, 03-1d

018

実装してみた

以下の順番で実装します。

  • 実行ロール作成
  • Lambda 関数作成

EC2 Auto Scaling の対象グループ内で起動されている EC2 インスタンスの判断は作成時に自動で付与される aws:autoscaling:groupName タグ記載の Values を基にしています。

実行ロール

信頼関係
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "lambda.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
アタッチするポリシー例
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "arn:aws:logs:<region>:<account-id>:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:<region>:<account-id>:log-group:/aws/lambda/<function-name>:*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances",
                "ec2:CreateTags",
                "ec2:DescribeTags",
                "autoscaling:DescribeAutoScalingGroups"
            ],
            "Resource": "*"
        }
    ]
}

※ 適宜修正してください。

Lambda 関数

Python 3.13 で作成しました。
実行ロールでは、既存のロールを使用するを選択し、先ほど作成したロールを指定します。

2AZ で実装する Lambda 関数例
import boto3
from datetime import datetime
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    ec2_client = boto3.client('ec2')

    try:
        # AutoScalingグループのタグを持つEC2インスタンスを取得
        instances = ec2_client.describe_instances(
            Filters=[
                {'Name': 'tag:aws:autoscaling:groupName', 'Values': ['xxxxxxx']},
                {'Name': 'instance-state-name', 'Values': ['running']}
            ]
        )

        # AZごとにインスタンスを分類
        az_instances = {
            'ap-northeast-1a': [],  # 奇数用
            'ap-northeast-1c': []   # 偶数用
        }

        # インスタンスをAZごとに振り分け
        for reservation in instances['Reservations']:
            for instance in reservation['Instances']:
                az = instance['Placement']['AvailabilityZone']
                if az in az_instances:
                    az_instances[az].append({
                        'InstanceId': instance['InstanceId'],
                        'LaunchTime': instance['LaunchTime'],
                        'AZ': az
                    })

        # 各AZでの処理
        for az, az_list in az_instances.items():
            if not az_list:
                continue

            # 起動時間でソート
            sorted_instances = sorted(az_list, key=lambda x: x['LaunchTime'])

            # AZに基づいて開始番号を決定
            if az.endswith('a'):
                # ap-northeast-1a は奇数
                start_number = 1
                increment = 2
            else:
                # ap-northeast-1c は偶数
                start_number = 2
                increment = 2

            # インスタンスにタグ付け
            for i, instance in enumerate(sorted_instances):
                number = start_number + (i * increment)
                instance_name = f"your-prefix-{number:02d}"

                # 現在のタグを確認
                current_tags = ec2_client.describe_tags(
                    Filters=[
                        {'Name': 'resource-id', 'Values': [instance['InstanceId']]},
                        {'Name': 'key', 'Values': ['Name']}
                    ]
                )

                current_name = None
                if current_tags['Tags']:
                    current_name = current_tags['Tags'][0]['Value']

                # タグが異なる場合のみ更新
                if current_name != instance_name:
                    ec2_client.create_tags(
                        Resources=[instance['InstanceId']],
                        Tags=[{'Key': 'Name', 'Value': instance_name}]
                    )
                    logger.info(f"Tagged instance {instance['InstanceId']} in {az} with {instance_name} (was {current_name})")

        return {
            'statusCode': 200,
            'body': 'Successfully updated instance tags'
        }

    except Exception as e:
        logger.error(f"Error: {str(e)}")
        raise
3AZ で実装する Lambda 関数例
import boto3
from datetime import datetime
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    ec2_client = boto3.client('ec2')

    try:
        # AutoScalingグループのタグを持つEC2インスタンスを取得
        instances = ec2_client.describe_instances(
            Filters=[
                {'Name': 'tag:aws:autoscaling:groupName', 'Values': ['xxxxxxx']},
                {'Name': 'instance-state-name', 'Values': ['running']}
            ]
        )

        # AZごとにインスタンスを分類
        az_instances = {
            'ap-northeast-1a': [],
            'ap-northeast-1c': [],
            'ap-northeast-1d': []
        }

        # インスタンスをAZごとに振り分け
        for reservation in instances['Reservations']:
            for instance in reservation['Instances']:
                az = instance['Placement']['AvailabilityZone']
                if az in az_instances:
                    az_instances[az].append({
                        'InstanceId': instance['InstanceId'],
                        'LaunchTime': instance['LaunchTime'],
                        'AZ': az
                    })

        # 各AZでの処理
        for az, az_list in az_instances.items():
            if not az_list:
                continue

            # 起動時間でソート
            sorted_instances = sorted(az_list, key=lambda x: x['LaunchTime'])

            # AZサフィックスを取得 (例:1a, 1c, 1d)
            az_suffix = az[-2:]

            # インスタンスにタグ付け
            for i, instance in enumerate(sorted_instances):
                number = i + 1  # 1から始まる連番
                instance_name = f"your-prefix-{number:02d}-{az_suffix}"

                # 現在のタグを確認
                current_tags = ec2_client.describe_tags(
                    Filters=[
                        {'Name': 'resource-id', 'Values': [instance['InstanceId']]},
                        {'Name': 'key', 'Values': ['Name']}
                    ]
                )

                current_name = None
                if current_tags['Tags']:
                    current_name = current_tags['Tags'][0]['Value']

                # タグが異なる場合のみ更新
                if current_name != instance_name:
                    ec2_client.create_tags(
                        Resources=[instance['InstanceId']],
                        Tags=[{'Key': 'Name', 'Value': instance_name}]
                    )
                    logger.info(f"Tagged instance {instance['InstanceId']} in {az} with {instance_name} (was {current_name})")

        return {
            'statusCode': 200,
            'body': 'Successfully updated instance tags'
        }

    except Exception as e:
        logger.error(f"Error: {str(e)}")
        raise

※ 適宜修正してください。
特に、EC2 Auto Scaling 作成時に自動で付与される aws:autoscaling:groupName タグ記載の Values': ['xxxxxxx'] にはご自身の値を記載してください。

001

これで、実装は完了です。お疲れさまでした。

検証してみた

EC2 Auto Scaling を作成し、EC2 を起動させます。

003

002

それでは 2AZ の Lambda 関数をテストします。

004

成功したので、タグ付けされているか確認します。タグ付けされているので、成功です!

005

この後、02 と 03 が付与されている EC2 インスタンスを削除して、希望するキャパシティーを 6 台に変更します。
006

007

008

既存の 2 台を含め 6 台で起動されたので、再度 Lambda 関数を実行してタグの修正と追加されるか試します。

009

成功です!
010

続いて 3AZ の Lambda 関数も同様にテストします。
011

012

それでは 3AZ の Lambda 関数をテストします。

013

成功です!

014

この後適当に EC2 インスタンスを削除して、希望するキャパシティーを 9 台に変更します。

015

016

017

再度 Lambda 関数を実行してタグの修正と追加されるか試します。
成功です!

018

まとめ

Eventbridge を利用して EC2 インスタンスの起動などをトリガーに該当 Lambda 関数を呼び出す設定や、スケジュール機能を利用して定期実行すれば完全自動で付与可能です。
本ブログが誰かの参考になれば幸いです。

参考資料

アノテーション株式会社について

アノテーション株式会社は、クラスメソッド社のグループ企業として「オペレーション・エクセレンス」を担える企業を目指してチャレンジを続けています。「らしく働く、らしく生きる」のスローガンを掲げ、様々な背景をもつ多様なメンバーが自由度の高い働き方を通してお客様へサービスを提供し続けてきました。現在当社では一緒に会社を盛り上げていただけるメンバーを募集中です。少しでもご興味あれば、アノテーション株式会社WEBサイトをご覧ください。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.